#pragma once

#include<iostream>
#include<mutex>
#include<fstream>
#include<string>
#include<vector>
#include<unordered_map>
#include<utility>
#include"util.hpp"
#include"log.hpp"

//设置字体颜色
#ifndef COLOR
#define COLOR
#define RESET "\033[0m"
#define RED "\033[31m"    /* Red */
#define GREEN "\033[32m"   /* Green */
#endif

namespace ns_index {

    struct DocInfo {
        std::string title;
        std::string content;
        std::string url;
        int doc_id;           //文档ID
    };

    struct InvertElem {
        int doc_id;
        std::string word;       //关键字
        int weight;             //权重

    };

    //倒排拉链
    typedef std::vector<InvertElem> InvertList;

    class Index {
    public:
        ~Index() {

        }
        static Index* GetInstance() {
            //外层判断可以减少多次申请锁的开销
            if (nullptr == instance_) {
                mtx_.lock();
                if (nullptr == instance_) {
                    instance_ = new Index();
                    mtx_.unlock();
                }
            }

            return instance_;
        }

        //根据DocId找到文档内容
        DocInfo* GetForwardIndex(int doc_id) {
            //文档ID不合法，输出错误信息
            if (doc_id >= forward_index_.size()) {
                std::cerr << RED << "文档ID不合法" << RESET << std::endl;
                return nullptr;
            }
            return &forward_index_[doc_id];
        }
        //根据关键字获取倒排拉链
        InvertList* GetInvertList(const std::string& word) {
            auto iter = invert_index_.find(word);

            if (iter == invert_index_.end()) {
                std::cerr << RED << "该关键字无对应倒排索引" << RESET << std::endl;
                return nullptr;
            }
            return &(iter->second);
        }
        //建立索引(根据我们已经去标签化的文件来建立索引)
        bool BuildIndex(const std::string& input) {

            std::ifstream in(input, std::ios::in | std::ios::binary);
            if (!in.is_open()) {
                std::cerr << RED << "BuildIndex file open failed: " << input << RESET << std::endl;
                return false;
            }
            //打开成功,按行读取
            std::string line;
            int count = 0;
            while (std::getline(in, line)) {
                DocInfo* doc = BuildForwardIndex(line);
                if (nullptr == doc) {
                    std::cerr << RED << "Build forward index failed!" << std::endl;
                    continue;
                }
                BuildInvertIndex(*doc);
                // ++count;
                // if (count % 100 == 0) {
                //     std::cout << "当前已经建立的索引文档：" << count << std::endl; 
                // }

            }
            // std::cout << GREEN << "索引建立成功" << RESET << std::endl;
            // std::cout << "正排索引数量：" << forward_index_.size() << std::endl;
            // std::cout << "倒排索引数量：" << invert_index_.size() << std::endl;

            LOG(NORMAL, "索引建立成功！");
            LOG(NORMAL, "正排索引数量：" + std::to_string(forward_index_.size()));
            LOG(NORMAL, "倒排索引数量：" + std::to_string(invert_index_.size()));



            return true;
        }
    private:
        Index() {}
        Index(const Index&) = delete;
        Index& operator=(const Index&) = delete;

        DocInfo* BuildForwardIndex(const std::string& line) {
            //构建DocInfo并插入正排索引的vector中
            //1.解析line，切分内容
            std::vector<std::string> result;
            const std::string sep = "\3";
            if (!ns_util::StringUtil::Split(line, &result, sep)) {
                std::cout << RED << "Split failed!" << RESET << std::endl;
                return nullptr;
            }
            //2.填充DocInfo
            DocInfo doc;
            doc.title = result[0];
            doc.content = result[1];
            doc.url = result[2];        //url之后还有一个\n，我觉得应该去掉 (实际操作下来，发现\n并不会被切分到字符串中，所以不用pop_back())
            doc.doc_id = forward_index_.size();
            //3.插入正排索引容器中
            forward_index_.emplace_back(doc);

            return &forward_index_.back();
        }

        struct word_cnt {
            int title_cnt;
            int content_cnt;
            word_cnt() :title_cnt(0), content_cnt(0) {}
        };
        //建立倒排索引
        void BuildInvertIndex(const DocInfo& doc) {
            //先分词，同时做词频统计
            std::unordered_map<std::string, word_cnt> word_map;
            //对标题分词
            std::vector<std::string> title_words;
            ns_util::JiebaUtil::CurStr(doc.title, &title_words);
            //统计词频
            for (std::string& word : title_words) {
                //搜索和查词时不区分大小写
                boost::to_lower(word);
                ++word_map[word].title_cnt;
            }
            // if (doc.doc_id == 7745) {
            //     std::cout << "title: " << std::endl;
            //     for (int i = 0; i < title_words.size(); ++i) {
            //         std::cout << title_words[i] << std::endl;
            //     }
            // }

            //对内容分词
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::CurStr(doc.content, &content_words);
            //统计词频
            for (std::string& word : content_words) {
                boost::to_lower(word);
                ++word_map[word].content_cnt;
            }

            // if (doc.doc_id == 7745) {
            //     std::cout << "content: " << std::endl;
            //     for (int i = 0; i < content_words.size(); ++i) {
            //         std::cout << content_words[i] << std::endl;
            //     }
            // }

            //构建倒排索引表元素，并加入倒排索引表
            for (auto& word_pair : word_map) {
                InvertElem item;
                item.word = word_pair.first;
                item.doc_id = doc.doc_id;
                item.weight = word_pair.second.title_cnt * 100 + word_pair.second.content_cnt;

                invert_index_[word_pair.first].emplace_back(item);
            }
        }
        //正排索引下标表示文档ID
        std::vector<DocInfo> forward_index_;  //  正排索引
        //倒排索引关键字和一组InvertElem的对应关系(关键字和倒排拉链的映射关系)
        std::unordered_map<std::string, InvertList> invert_index_;
        static Index* instance_;
        static std::mutex mtx_;

    };

    Index* Index::instance_ = nullptr;
    std::mutex Index::mtx_;
}
